package pers.yurwisher.rubick.common.config;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.StringUtils;
import zipkin2.reporter.Sender;
import zipkin2.reporter.amqp.RabbitMQSender;

import java.time.Duration;
import java.util.Objects;

/**
 * @author yq 2021/4/30 19:34
 * @description RabbitConfig 多 virtual-host支持
 */
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@ConditionalOnProperty(
        value = {"spring.zipkin.sender.type"},
        havingValue = "rabbit"
)
@Configuration
@Slf4j
public class CustomRabbitConfig {

    @Autowired
    private RabbitProperties rabbitProperties;
    @Value("${spring.zipkin.virtual-host}")
    private String zipkinVirtualHost;
    @Autowired
    private ObjectProvider<ConnectionNameStrategy> connectionNameStrategy;
    @Value("${spring.zipkin.rabbitmq.queue:zipkin}")
    private String queue;
    @Value("${spring.zipkin.rabbitmq.addresses:}")
    private String addresses;


    @Bean
    @Primary
    public CachingConnectionFactory rabbitConnectionFactory() throws Exception {
        log.info("auto inject rabbitConnectionFactory");
        return buildFactory(connectionNameStrategy, rabbitProperties.getVirtualHost());
    }

    @Bean
    public CachingConnectionFactory zipkinConnectionFactory() throws Exception {
        log.info("auto inject zipkinRabbitConnectionFactory");
        return buildFactory(connectionNameStrategy, zipkinVirtualHost);
    }

    private CachingConnectionFactory buildFactory(ObjectProvider<ConnectionNameStrategy> connectionNameStrategy, String virtualHost) throws Exception {
        PropertyMapper map = PropertyMapper.get();
        CachingConnectionFactory factory = new CachingConnectionFactory(Objects.requireNonNull(getRabbitConnectionFactoryBean(virtualHost).getObject()));
        map.from(rabbitProperties::determineAddresses).to(factory::setAddresses);
        map.from(rabbitProperties::isPublisherReturns).to(factory::setPublisherReturns);
        map.from(rabbitProperties::getPublisherConfirmType).whenNonNull().to(factory::setPublisherConfirmType);
        RabbitProperties.Cache.Channel channel = rabbitProperties.getCache().getChannel();
        map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
        map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis)
                .to(factory::setChannelCheckoutTimeout);
        RabbitProperties.Cache.Connection connection = rabbitProperties.getCache().getConnection();
        map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
        map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
        map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
        return factory;
    }

    /**
     * 创建 RabbitConnectionFactoryBean
     *
     * @param virtualHost 虚拟主机
     * @return 工厂bean
     * @throws Exception
     */
    private RabbitConnectionFactoryBean getRabbitConnectionFactoryBean(String virtualHost) {
        PropertyMapper map = PropertyMapper.get();
        RabbitConnectionFactoryBean factory = new RabbitConnectionFactoryBean();
        map.from(rabbitProperties::determineHost).whenNonNull().to(factory::setHost);
        map.from(rabbitProperties::determinePort).to(factory::setPort);
        map.from(rabbitProperties::determineUsername).whenNonNull().to(factory::setUsername);
        map.from(rabbitProperties::determinePassword).whenNonNull().to(factory::setPassword);
        map.from(virtualHost).whenNonNull().to(factory::setVirtualHost);
        map.from(rabbitProperties::getRequestedHeartbeat).whenNonNull().asInt(Duration::getSeconds)
                .to(factory::setRequestedHeartbeat);
        map.from(rabbitProperties::getRequestedChannelMax).to(factory::setRequestedChannelMax);
        RabbitProperties.Ssl ssl = rabbitProperties.getSsl();
        if (ssl.determineEnabled()) {
            factory.setUseSSL(true);
            map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
            map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
            map.from(ssl::getKeyStore).to(factory::setKeyStore);
            map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase);
            map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType);
            map.from(ssl::getTrustStore).to(factory::setTrustStore);
            map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
            map.from(ssl::isValidateServerCertificate)
                    .to((validate) -> factory.setSkipServerCertificateValidation(!validate));
            map.from(ssl::getVerifyHostname).to(factory::setEnableHostnameVerification);
        }
        map.from(rabbitProperties::getConnectionTimeout).whenNonNull().asInt(Duration::toMillis)
                .to(factory::setConnectionTimeout);
        factory.afterPropertiesSet();
        return factory;
    }

    @Bean({"zipkinSender"})
    Sender rabbitSender() throws Exception {
        log.info("auto inject zipkinSender");
        String addresses = StringUtils.hasText(this.addresses) ? this.addresses : rabbitProperties.determineAddresses();
        return RabbitMQSender.newBuilder().connectionFactory(zipkinConnectionFactory().getRabbitConnectionFactory()).queue(this.queue).addresses(addresses).build();
    }

    @Bean(name = "rabbitListenerContainerFactory")
    @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "direct")
    DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory(DirectRabbitListenerContainerFactoryConfigurer configurer) throws Exception {
        DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory();
        configurer.configure(factory, rabbitConnectionFactory());
        return factory;
    }

    @Bean(name = "rabbitListenerContainerFactory")
    @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true)
    SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer) throws Exception {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, rabbitConnectionFactory());
        return factory;
    }

}
